<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>File Management</title>
    <script src="static/vue.min.js"></script>
    <script src="static/axios.min.js"></script>

    <link rel="stylesheet" href="static/element-ui.css">
    <script src="static/element-ui.js"></script>
    <style>
        body {
            /* 添加内边距，避免内容被固定元素覆盖 */
            padding-bottom: 60px;
            margin: 20px;
        }

        #client-ip {
            position: fixed;
            bottom: 10px;
            right: 10px;
            color: gray;
        }

        .upload-container {
            margin: 20px 0;
            padding: 20px;
            border: 1px solid #ebeef5;
            border-radius: 4px;
        }

        .file-list-table {
            margin-top: 40px;
            /* 设置表格高度，可根据需要调整 */
            height: 60vh;
            overflow-y: auto;
        }

        .progress-cell {
            min-width: 200px;
        }

        /* 响应式表格样式 */
        @media (max-width: 768px) {

            .el-table__header-wrapper,
            .el-table__body-wrapper {
                overflow-x: auto;
            }
        }

        /* 降低表格行高 */
        .el-table .el-table__row {
            height: 40px;
        }

        .el-table .el-table__cell {
            padding: 2px 0;
        }

        /* 让分页组件靠右显示 */
        .el-pagination {
            float: right;
        }

        /* 为 EditConfig 按钮添加样式 */
        .edit_btn {
            position: fixed;
            bottom: 10px;
            left: 10px;
        }

        /* 调整 el-tabs 样式 */
        .el-tabs--left {
            display: flex;
        }

        .el-tabs__nav-wrap.is-left {
            width: 200px;
        }

        .el-tabs__content {
            flex: 1;
            padding-left: 20px;
        }


        /* 调整 el-tabs 样式 */
        .el-tabs--left {
            display: flex;
        }

        /* 修改标签栏宽度 */
        .el-tabs__nav-wrap.is-left {
            width: 100px;
            /* 你可以根据需要调整这个值 */
        }

        .el-tabs__content {
            flex: 1;
            padding-left: 20px;
        }

        /* 新增动画样式 */
        @keyframes pulse {
            0% {
                background-color: rgba(0, 123, 255, 0.1);
            }

            50% {
                background-color: rgba(0, 123, 255, 0.3);
            }

            100% {
                background-color: rgba(0, 123, 255, 0.1);
            }
        }

        .dragover-indicator {
            animation: pulse 1.5s infinite;
        }
    </style>
</head>

<body>
    <div id="app" style="display: none;" :style="{display: 'block'}">

        <!-- 新增文字提示元素 -->
        <div id="drag-drop-message"
            style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: rgba(0, 0, 0, 0.7); color: white; padding: 20px; border-radius: 5px; z-index: 9999;">
            释放鼠标以上传文件
        </div>
        <!-- 添加隐藏的文件输入元素 -->
        <input type="file" id="fileInput" style="display: none;" @change="handleFileInputChange" multiple>

        <el-tabs tab-position="left">
            <el-tab-pane label="文件管理">
                <div style="display: flex; align-items: center;margin-bottom: 15px;">

                    <el-link type="primary" @click="goBackOneLevel" style="cursor: pointer;">返回上一级</el-link>
                    &nbsp;&nbsp;<span style="color: darkgray;">|</span>&nbsp;&nbsp;
                    <el-breadcrumb separator="/">
                        <el-breadcrumb-item @click="goToRoot" style="cursor: pointer;">根目录</el-breadcrumb-item>
                        <!-- 动态生成多级目录 -->
                        <el-breadcrumb-item v-for="(item, index) in uploadFolder.split('/').filter(Boolean)"
                            :key="index" @click="navigateToFolder(index)" style="cursor: pointer;">
                            {{ item }}
                        </el-breadcrumb-item>
                    </el-breadcrumb>
                </div>
                <div class="action-buttons" style="display: flex; align-items: center; justify-content: flex-end;">

                    <!-- 修改 el-link，添加点击事件 -->
                    <el-link type="danger" style="cursor: pointer; "
                        @click="openFileSelector">拖拽文件到任意地方或点击这里即可上传</el-link>
                    <!-- 为按钮添加一些左边距 -->
                    <el-button type="success" @click="startAllUploads" :disabled="uploadQueue.length === 0"
                        style="margin-left: 10px;">
                        开始上传 ({{ uploadQueue.length }})
                    </el-button>

                    <el-button type="success" @click="refreshFileList">刷新</el-button>
                    <!-- 创建文件夹按钮 -->
                    <el-button type="primary" @click="createFolder">创建文件夹</el-button>


                    <!-- 新增批量删除按钮 -->
                    <el-button type="danger" @click="batchDelete" :disabled="!selectedRows.length">
                        批量删除
                    </el-button>

                </div>

                <!-- 上传进度区域 -->
                <el-dialog class="upload-container" :visible.sync="uploadProgressDialogVisible">
                    <div v-for="file in activeUploads" :key="file.id" class="upload-item">
                        <div class="file-info">
                            <span class="filename">{{ file.name }}</span>
                            <el-tag :type="statusTagType[file.status]" size="small">
                                {{ file.status }}
                            </el-tag>
                        </div>
                        <div class="progress-info">
                            <el-progress :percentage="file.progress" :status="file.error ? 'exception' : ''"
                                :format="formatProgress" stroke-width="18" style="width: 100%; max-width: 100%;" />
                            <span class="speed" v-if="file.speed">@ {{ file.speed }}/s</span>
                            <el-button v-if="file.status === 'uploading'" @click="cancelUpload(file)" size="mini"
                                type="danger">
                                Cancel
                            </el-button>
                        </div>
                        <div v-if="file.error" class="error-message">
                            Error: {{ file.error }}
                        </div>
                    </div>
                </el-dialog>

                <!-- 文件列表表格 -->
                <el-table :data="fileList" stripe class="file-list-table" v-loading="loading"
                    @selection-change="handleSelectionChange">
                    <!-- 添加勾选框列 -->
                    <el-table-column type="selection" width="55"></el-table-column>
                    <!-- 添加序号列 -->
                    <el-table-column label="序号" width="60" align="center">
                        <template slot-scope="scope">
                            <!-- 计算序号 -->
                            {{ (currentPage - 1) * pageSize + scope.$index + 1 }}
                        </template>
                    </el-table-column>
                    <el-table-column prop="name" label="文件名称" width="500">
                        <template slot-scope="scope">
                            <!-- 根据文件扩展名显示图标 -->
                            <i :class="getIconClass(scope.row.type)" style="margin-right: 5px;"></i>
                            <span v-if="scope.row.type === '文件夹'" @click="handleFolderClick(scope.row)"
                                style="cursor: pointer; color: #2196F3;">
                                {{ scope.row.name }}
                            </span>
                            <span v-else>
                                {{ scope.row.name }}
                            </span>
                        </template>
                    </el-table-column>
                    <el-table-column prop="type" label="文件类型" width="90" align="center"></el-table-column>
                    <el-table-column prop="upload_time" label="更新时间" width="170" align="center"></el-table-column>
                    <el-table-column prop="download_count" label="下载量" width="70" align="center"></el-table-column>
                    <el-table-column prop="size" label="文件大小" width="100" align="center"></el-table-column>
                    <el-table-column label="操作" width="350" align="center" fixed="right">
                        <template slot-scope="scope">
                            <div class="action-buttons">
                                <el-button v-if="!isUserFile(scope.row)" size="mini" type="primary"
                                    icon="el-icon-copy-document" @click="copyDownloadLink(scope.row)">
                                    复制链接
                                </el-button>
                                <el-button v-if="!isUserFile(scope.row)" size="mini" icon="el-icon-download"
                                    @click="downloadFile(scope.row)">
                                    下载
                                </el-button>
                                <el-button v-if="showPreview(scope.row)" size="mini" type="danger"
                                    icon="el-icon-zoom-in" @click="previewFile(scope.row)">
                                    预览
                                </el-button>
                                <el-button v-if="canDel(scope.row)" class="danger-text" size="mini" type="danger"
                                    icon="el-icon-delete" @click="confirmDelete(scope.row)">
                                    删除
                                </el-button>
                                <el-button v-if="isJarFile(scope.row.name)" size="mini" type="danger"
                                    @click="showDeploymentDialog(scope.row)">
                                    发布
                                </el-button>
                            </div>
                        </template>
                    </el-table-column>
                </el-table>
                <!-- 分页组件 -->
                <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
                    :current-page="currentPage" :page-sizes="[10, 20, 30, 40, 50, 100, 1000]" :page-size="pageSize"
                    layout="total, sizes, prev, pager, next, jumper" :total="total">
                </el-pagination>

                <!-- 部署对话框 -->
                <el-dialog title="Select Deployment Targets" :visible.sync="deploymentDialogVisible" width="60%">
                    <server-table ref="serverTable" @selection-change="handleServerSelection" />
                    <span slot="footer">
                        <el-button @click="deploymentDialogVisible = false">取消</el-button>
                        <el-button type="primary" @click="startDeployment" :loading="deploying">
                            {{ deploying ? '部署中...' : '开始部署' }}
                        </el-button>
                    </span>
                </el-dialog>

            </el-tab-pane>
            <el-tab-pane label="共享剪贴板">
                <div style="padding: 20px;">
                    <!-- 顶部标题和统计 -->
                    <div
                        style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px;">
                        <div>
                            <h2 style="margin: 0; color: #303133; font-size: 24px;">
                                <i class="el-icon-document-copy" style="color: #409EFF; margin-right: 10px;"></i>
                                共享剪贴板
                            </h2>
                            <p style="margin: 5px 0 0 0; color: #909399; font-size: 14px;">
                                共 {{ clipboardList.length }} 条记录 · 点击卡片即可复制内容
                            </p>
                        </div>
                        <el-button type="primary" icon="el-icon-refresh" @click="fetchClipboardList" circle></el-button>
                    </div>

                    <!-- 输入区域 - 卡片式设计 -->
                    <el-card shadow="hover" style="margin-bottom: 30px; border-radius: 12px;">
                        <div slot="header">
                            <span><i class="el-icon-edit" style="margin-right: 5px;"></i>添加新内容</span>
                        </div>
                        <div style="padding: 10px 0;">
                            <el-input v-model="clipboardInput" type="textarea" :rows="4"
                                placeholder="📝 在这里粘贴或输入您要分享的内容..." maxlength="2000" show-word-limit
                                style="margin-bottom: 15px;">
                            </el-input>
                            <div style="display: flex; gap: 10px; justify-content: flex-end;">
                                <el-button @click="clearClipboard" icon="el-icon-delete" plain>清空</el-button>
                                <el-button type="primary" @click="saveClipboardContent" icon="el-icon-plus"
                                    :disabled="!clipboardInput.trim()">
                                    添加到剪贴板
                                </el-button>
                            </div>
                        </div>
                    </el-card>

                    <!-- 剪贴板内容列表 - 卡片式布局 -->
                    <div v-if="clipboardList.length === 0" style="text-align: center; padding: 60px 0;">
                        <i class="el-icon-document-delete" style="font-size: 48px; color: #DCDFE6;"></i>
                        <p style="color: #909399; margin-top: 10px;">暂无共享内容，快来添加第一条吧！</p>
                    </div>

                    <div v-else
                        style="display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px;">
                        <el-card v-for="(item, index) in clipboardList" :key="index" class="clipboard-card"
                            shadow="hover" style="border-radius: 12px; cursor: pointer; transition: all 0.3s ease;">
                            <div style="padding: 15px;">
                                <!-- 内容预览 -->
                                <div style="margin-bottom: 15px;">
                                    <div
                                        style="font-size: 14px; line-height: 1.6; color: #606266; 
                                                max-height: 120px; overflow: hidden; 
                                                display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical;">
                                        {{ item.content }}
                                    </div>
                                </div>

                                <!-- 底部信息栏 -->
                                <div style="display: flex; justify-content: space-between; align-items: center; 
                                            border-top: 1px solid #EBEEF5; padding-top: 12px;">
                                    <div style="display: flex; align-items: center; gap: 8px;">
                                        <el-tag size="mini" type="info">
                                            <i class="el-icon-time" style="margin-right: 3px;"></i>
                                            {{ item.time }}
                                        </el-tag>
                                        <el-tag size="mini" type="success">
                                            {{ item.content.length }} 字符
                                        </el-tag>
                                    </div>
                                    <div>
                                        <el-button type="text" icon="el-icon-document-copy"
                                            @click="copyToClipboard(item.content)" style="padding: 5px;">
                                            复制
                                        </el-button>
                                        <el-button type="text" icon="el-icon-delete" @click="deleteClipboardItem(index)"
                                            style="padding: 5px; color: #F56C6C;">
                                            删除
                                        </el-button>
                                    </div>
                                </div>
                            </div>
                        </el-card>
                    </div>
                </div>
            </el-tab-pane>

            <el-tab-pane label="运维工具">
                <div style="display: flex; align-items: center;">
                    <el-input v-model="oaAccount" placeholder="请输入OA账号"
                        style="width: 200px; margin-right: 10px;"></el-input>
                    <el-button type="primary" @click="openOApage">登录</el-button>

                </div>
                <div style="display: flex; align-items: center; margin-top: 10px;">
                    <!-- 添加三个单选按钮 -->
                    <el-radio-group v-model="selectedOption">
                        <el-radio label="/web-v1">科研平台</el-radio>
                        <el-radio label="/hrm-view">人资系统</el-radio>
                        <el-radio label="/model">数据系统</el-radio>
                        <el-radio label="/dataProject">模型系统</el-radio>
                    </el-radio-group>
                </div>
                <!-- 添加人员列表 -->
                <el-table :data="oaConfig.personList" stripe @row-click="selectPerson" style="margin-top: 20px;">
                    <el-table-column prop="name" width="200" label="人员姓名"></el-table-column>
                    <el-table-column prop="iscAccount" width="200" label="OA账号"></el-table-column>
                </el-table>
            </el-tab-pane>
        </el-tabs>


        <!-- 其他对话框和组件 -->
        <div id="client-ip">{{ clientIp }}</div>


        <!-- 新增升级程序按钮和文件输入框 -->
        <div class="edit_btn">
            <input type="file" id="upgrade-file-input" style="display: none;" @change="handleUpgradeFileSelect">
            <el-button type="primary" icon="el-icon-cpu" @click="openUpgradeFileDialog">更新程序</el-button>
            <!-- EditConfig 按钮 -->
            <el-button type="primary" class="edit-config-button" icon="el-icon-setting"
                @click="openEditConfigDialog">编辑配置</el-button>

            <!-- 为输入框添加一些左边距 -->
            <el-input v-model="uploadFolder" placeholder="当前路径"
                style="width: 200px; margin-left: 10px; margin-right: 10px;"></el-input>
        </div>


        <!-- 编辑配置对话框 -->
        <el-dialog title="编辑配置" :visible.sync="editConfigDialogVisible" width="60%">
            <el-input type="textarea" v-model="configText" rows="20"></el-input>
            <span slot="footer">
                <el-button @click="editConfigDialogVisible = false">取消</el-button>
                <el-button type="primary" @click="saveConfig">保存</el-button>
            </span>
        </el-dialog>
    </div>

    <script>
        const CHUNK_SIZE = 3 * 1024 * 1024; // 5MB
        const MAX_RETRIES = 3;   // 最大重试次数

        new Vue({
            el: '#app',
            data() {
                return {
                    // 上传相关状态
                    uploadQueue: [],
                    activeUploads: [],
                    nextFileId: 1,
                    uploadFolder: '',

                    // 文件列表
                    fileList: [],
                    loading: false,

                    // 部署相关
                    deploymentDialogVisible: false,
                    deploying: false,
                    selectedFile: null,
                    selectedServers: [],

                    // 系统配置
                    clientIp: 'Loading IP...',
                    allowedPreview: ['pdf', 'jpg', 'png', 'txt'],
                    statusTagType: {
                        pending: 'info',
                        uploading: 'primary',
                        success: 'success',
                        error: 'danger',
                        canceled: 'warning'
                    },
                    // 分页相关
                    currentPage: 1,
                    pageSize: 10,
                    total: 0,

                    // 编辑配置相关
                    editConfigDialogVisible: false,
                    configText: '',

                    // 定义文件格式与图标的映射关系
                    fileIconMap: {
                        'pdf': 'el-icon-document',
                        'jpg': 'el-icon-picture',
                        'png': 'el-icon-picture',
                        'txt': 'el-icon-document',
                        'jar': 'el-icon-cpu',
                        'gz': 'el-icon-coin',
                        'zip': 'el-icon-coin',
                        'rar': 'el-icon-coin',
                        '文件夹': 'el-icon-folder-opened'
                    },
                    defaultIconClass: 'el-icon-document', // 默认图标类名
                    // 人员ISC账号
                    oaAccount: '',
                    // 新增的用于记录选中单选按钮值的数据属性
                    selectedOption: "/web-v1",
                    oaConfig: {
                        sendUrl: '',
                        selectedOptions: [],
                        personList: []
                    },
                    selectedRows: [], // 存储选中的行

                    clipboardList: [], // 剪贴板列表
                    clipboardInput: '', // 剪贴板输入框内容
                    uploadProgressDialogVisible: false // 控制上传进度弹出框的显示与隐藏
                }
            },
            mounted() {
                this.checkLoginStatus();
                this.fetchConfig()
                this.fetchFileList()
                this.fetchClientIp()
                this.fetchClipboardList() // 获取剪贴板列表

                // 添加拖放事件监听器
                window.addEventListener('dragover', this.handleDragOver);
                window.addEventListener('dragleave', this.handleDragLeave);
                window.addEventListener('drop', this.handleDrop);
            },
            beforeDestroy() {
                // 移除事件监听器以避免内存泄漏
                window.removeEventListener('dragover', this.handleDragOver);
                window.removeEventListener('dragleave', this.handleDragLeave);
                window.removeEventListener('drop', this.handleDrop);
            },
            methods: {
                // 处理表格行选中事件
                handleSelectionChange(rows) {
                    this.selectedRows = rows;
                },
                // 新增检查登录状态的方法
                checkLoginStatus() {
                    axios.get('/api-v1/mirco_project/util/lock/list')
                        .then(response => {
                            // 如果返回200，表示已登录，继续正常加载
                            if (response.status === 200) {
                                // 可以在这里添加额外的登录验证逻辑
                                console.log('用户已登录');
                            }
                        })
                        .catch(error => {
                            // 检查当前域名是否为20.164开头的IP地址
                            const currentHostname = window.location.hostname;
                            const is20_164_IP = /^20\.164/.test(currentHostname);
                            const is127_IP = /^127\.0\.0\.1/.test(currentHostname);

                            // 如果不是20.164开头的IP地址，才进行跳转
                            if (!is20_164_IP && !is127_IP) {
                                this.$message.error('请先登录统一平台');
                                // 跳转到根目录
                                window.location.href = '/kypt/serve';
                            } else {
                                console.log('当前为20.164开头的IP地址，不跳转');
                            }
                        });
                },
                // 新增：获取剪贴板列表
                async fetchClipboardList() {
                    try {
                        const response = await axios.get('get_clipboard_list');
                        const data = response.data;
                        if (data.code === 0) {
                            this.clipboardList = data.data;
                        } else {
                            this.$message.error(data.msg);
                        }
                    } catch (error) {
                        this.$message.error('获取剪贴板列表时发生错误');
                    }
                },

                // 复制到剪贴板
                async copyToClipboard(text) {
                    try {
                        await navigator.clipboard.writeText(text);
                        this.$message.success('已复制到剪贴板！');
                    } catch (err) {
                        // 降级方案
                        const textArea = document.createElement('textarea');
                        textArea.value = text;
                        textArea.style.position = 'fixed';
                        textArea.style.opacity = 0;
                        document.body.appendChild(textArea);
                        textArea.select();
                        document.execCommand('copy');
                        document.body.removeChild(textArea);
                        this.$message.success('已复制到剪贴板！');
                    }
                },

                // 删除剪贴板项
                async deleteClipboardItem(index) {
                    try {
                        await this.$confirm('确定要删除这条记录吗？', '确认删除', {
                            confirmButtonText: '确定',
                            cancelButtonText: '取消',
                            type: 'warning'
                        });

                        const item = this.clipboardList[index];
                        await axios.post('delete_clipboard_item', { time: item.time });
                        this.clipboardList.splice(index, 1);
                        this.$message.success('删除成功！');
                    } catch (error) {
                        if (error !== 'cancel') {
                            this.$message.error('删除失败：' + error.message);
                        }
                    }
                },
                handleRowClick(row) {
                    this.copyToClipboard(row.content);
                },
                // 复制剪贴板内容
                async copyClipboardContent(content) {
                    try {
                        if (navigator.clipboard && window.isSecureContext) {
                            // 使用 navigator.clipboard 复制内容
                            await navigator.clipboard.writeText(content);
                            this.$message.success('内容已复制到剪贴板');
                        } else {
                            // 兼容性处理
                            const textArea = document.createElement('textarea');
                            textArea.value = content;
                            textArea.style.position = 'absolute';
                            textArea.style.opacity = 0;
                            textArea.style.left = '-999999px';
                            textArea.style.top = '-999999px';
                            document.body.appendChild(textArea);
                            textArea.focus();
                            textArea.select();
                            document.execCommand('copy');
                            textArea.remove();
                            this.$message.success('内容已复制到剪贴板');
                        }
                    } catch (error) {
                        this.$message.error('复制内容时发生错误');
                    }
                },
                clearClipboard() {
                    for (let i = 0; i < 5; i++) {
                        this.clipboardInput += '     ';
                        this.saveClipboardContent();
                    }
                    this.fetchClipboardList();
                },
                async saveClipboardContent() {
                    const content = this.clipboardInput;
                    if (content) {
                        try {
                            const response = await axios.post('save_clipboard_content', { content });
                            const data = response.data;
                            if (data.code === 0) {
                                this.$message.success('剪贴板内容保存成功');
                                this.clipboardInput = '';
                                this.getClipboardList();
                            } else {
                                this.$message.error(data.msg);
                            }
                        } catch (error) {
                            this.$message.error('保存剪贴板内容时发生错误');
                        }
                    } else {
                        this.$message.error('内容为空');
                    }
                },
                async getClipboardList() {
                    try {
                        const response = await axios.get('get_clipboard_list');
                        const data = response.data;
                        if (data.code === 0) {
                            this.clipboardList = data.data;
                        } else {
                            this.$message.error(data.msg);
                        }
                    } catch (error) {
                        this.$message.error('获取剪贴板列表时发生错误');
                    }
                },
                async startAllUploads() {
                    console.log('startAllUploads 方法被调用，当前 uploadQueue 长度:', this.uploadQueue.length);
                    this.uploadProgressDialogVisible = true; // 显示上传进度弹出框
                    while (this.uploadQueue.length > 0) {
                        // 取文件开始上传
                        const file = this.uploadQueue.shift();
                        this.activeUploads.push(file);
                        this.processFileUpload(file);
                    }
                },
                async processFileUpload(file) {
                    if (file.status === 'canceled') return;

                    try {
                        file.status = 'uploading';
                        await this.uploadChunks(file);
                        file.status = 'success';
                        this.fetchFileList();
                        // 上传成功提示
                        this.$message.success(`${file.name} 上传成功`);
                    } catch (error) {
                        if (file.cancelToken.canceled) {
                            file.status = 'canceled';
                        } else {
                            file.status = 'error';
                            file.error = error.message;
                            this.$message.error(`${file.name} 上传失败: ${error.message}`);
                        }
                    } finally {
                        // 检查是否所有上传都已完成
                        const allUploadsCompleted = this.activeUploads.every(upload => upload.status === 'success' || upload.status === 'error');
                        if (allUploadsCompleted) {
                            this.uploadProgressDialogVisible = false; // 所有上传完成后关闭弹出框
                        }
                    }
                },
                // 上传文件分片
                async uploadChunks(file) {
                    console.log(file.name + '开始上传, 总片数：' + file.chunks);
                    const chunkQueue = Array.from({ length: file.chunks }, (_, i) => i);

                    while (chunkQueue.length > 0) {
                        if (file.cancelToken.canceled) {
                            throw new Error('取消上传');
                        }
                        // 取下一片
                        const chunkIndex = chunkQueue.shift();
                        await this.uploadChunkWithRetry(file, chunkIndex);
                        this.updateProgress(file);
                    }
                },
                // 上传分片，带重试机制
                async uploadChunkWithRetry(file, chunkIndex, attempt = 1) {
                    try {
                        await this.uploadSingleChunk(file, chunkIndex);
                    } catch (error) {
                        // 如果是取消上传的错误，直接抛出
                        // 如果是其他错误，重试，最大重试MAX_RETRIES次
                        if (attempt <= MAX_RETRIES) {
                            console.warn(`Retrying chunk ${chunkIndex} (attempt ${attempt})`);
                            await this.uploadChunkWithRetry(file, chunkIndex, attempt + 1);
                        } else {
                            throw new Error(`Chunk ${chunkIndex} failed after ${MAX_RETRIES} attempts: ${error.message}`);
                        }
                    }
                },

                async uploadSingleChunk(file, chunkIndex) {
                    // 计算分片开始位置
                    const start = chunkIndex * CHUNK_SIZE;
                    // 计算分片结束位置
                    const end = Math.min(start + CHUNK_SIZE, file.size);
                    // 计算分片大小
                    const chunk = file.rawFile.slice(start, end);

                    const formData = new FormData();
                    formData.append('file', chunk);
                    formData.append('chunk_index', chunkIndex);
                    formData.append('total_chunks', file.chunks);
                    formData.append('filename', file.name);
                    formData.append('uploadId', file.id);
                    formData.append('folder', this.uploadFolder);

                    const startTime = Date.now();
                    const response = await fetch('upload_file', {
                        method: 'POST',
                        body: formData
                    });

                    if (!response.ok) {
                        throw new Error(`HTTP ${response.status}`);
                    }

                    const data = await response.json();
                    if (data.code !== 0) {
                        throw new Error(data.msg);
                    }

                    // 计算上传速度
                    const duration = (Date.now() - startTime) / 1000;
                    const speed = (CHUNK_SIZE / 1024 / 1024 / duration).toFixed(1) + 'MB';
                    file.speed = speed;
                    file.lastUpdate = Date.now();
                },
                async createFolder() {
                    try {
                        if (this.uploadFolder.includes('page')) {
                            this.$message.error('不能在 page 文件夹下创建文件夹');
                            return;
                        }
                        const { value: folderName } = await this.$prompt('请输入文件夹名称', '创建文件夹', {
                            confirmButtonText: '确定',
                            cancelButtonText: '取消',
                        });

                        if (folderName) {
                            const folderPath = `${this.uploadFolder}/${folderName}`.replace(/^\/+/, '');
                            // 修改为使用 axios 发送请求
                            try {
                                const response = await axios.post('create_folder', {
                                    folderPath: folderPath
                                });

                                const data = response.data;
                                if (data.code === 0) {
                                    this.$message.success('文件夹创建成功');
                                    this.fetchFileList();
                                } else {
                                    throw new Error(data.msg);
                                }
                            } catch (error) {
                                if (error.response) {
                                    throw new Error(`HTTP ${error.response.status}`);
                                } else {
                                    throw error;
                                }
                            }
                        }
                    } catch (error) {
                        if (error !== 'cancel') {
                            this.$message.error('文件夹创建失败: ' + error.message);
                        }
                    }
                },
                updateProgress(file) {
                    file.uploadedChunks++;
                    file.progress = Math.floor((file.uploadedChunks / file.chunks) * 100);
                },

                cancelUpload(file) {
                    file.cancelToken.canceled = true;
                    this.uploadQueue = this.uploadQueue.filter(f => f.id !== file.id);
                    this.activeUploads = this.activeUploads.filter(f => f.id !== file.id);
                },

                // 刷新
                refreshFileList() {
                    this.fetchFileList();
                    this.$message.success(`刷新成功`);
                },
                // 获取文件列表
                fetchFileList() {
                    this.loading = true;
                    try {
                        axios.get('get_file_list', {
                            params: {
                                page: this.currentPage,
                                limit: this.pageSize,
                                folder: this.uploadFolder
                            }
                        }).then(response => {
                            const data = response.data;
                            this.fileList = data.data;
                            this.total = data.total;
                        }).catch(error => {
                            console.error('文件列表获取失败:', error);
                            this.$message.error('文件列表获取失败');
                        }).finally(() => {
                            this.loading = false;
                        });
                    } catch (error) {
                        console.error('文件列表获取失败:', error);
                        this.$message.error('文件列表获取失败');
                    }
                },
                // 分页方法
                handleSizeChange(newSize) {
                    this.pageSize = newSize;
                    this.fetchFileList();
                },
                handleCurrentChange(newPage) {
                    this.currentPage = newPage;
                    this.fetchFileList();
                },
                canDel(row) {
                    return !row.path.includes('page')
                },
                isUserFile(row) {
                    // 判断 path 是否包含 page
                    return row.path.includes('page') || row.type == '文件夹';
                },
                showPreview() {

                },
                isJarFile() {
                    return false;
                },
                formatProgress() {

                },
                fetchClientIp() {
                    axios.get('get_client_ip')
                        .then(response => {
                            const data = response.data;
                            this.clientIp = data.ip;
                        })
                        .catch(error => {
                            this.$message.error('Failed to fetch client IP');
                        })
                        .finally(() => {
                            this.loading = false;
                        });
                },
                downloadFile(file) {
                    window.open(`${window.location.href}download_file/${file.name}`, '_blank');
                },
                copyDownloadLink(file) {
                    // 拼接下载链接
                    const downloadLink = `${window.location.href}download_file/${(this.uploadFolder == null || this.uploadFolder == "") ? "" : (this.uploadFolder + "/")}${file.name}`;
                    // 检查浏览器是否支持复制功能
                    // navigator clipboard 需要https等安全上下文
                    if (navigator.clipboard && window.isSecureContext) {
                        // navigator clipboard 向剪贴板写文本
                        this.$message.success('浏览器支持复制功能，已复制下载链接');
                        return navigator.clipboard.writeText(downloadLink);
                    } else {
                        // 创建text area
                        let textArea = document.createElement("textarea");
                        textArea.value = downloadLink;
                        // 使text area不在viewport，同时设置不可见
                        textArea.style.position = "absolute";
                        textArea.style.opacity = 0;
                        textArea.style.left = "-999999px";
                        textArea.style.top = "-999999px";
                        document.body.appendChild(textArea);
                        textArea.focus();
                        textArea.select();
                        this.$message.success('浏览器不支持复制功能，已通过其他方式复制下载链接');
                        return new Promise((res, rej) => {
                            // 执行复制命令并移除文本框
                            document.execCommand('copy') ? res() : rej();
                            textArea.remove();
                        });
                    }

                },
                async confirmDelete(file) {
                    try {
                        await this.$confirm(`确认要删除【${file.name}】吗?`, '删除', { type: 'warning' });
                        await fetch('delete_file', {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            body: JSON.stringify({ filename: file.name })
                        });
                        this.fetchFileList();
                        this.$message.success('删除成功');
                    } catch (error) {
                        console.log('删除失败');
                    }
                },
                // 批量删除方法
                async batchDelete() {
                    try {
                        await this.$confirm('确认要删除选中的文件吗?', '批量删除', { type: 'warning' });
                        const validRows = this.selectedRows.filter(this.canDel);
                        const invalidRows = this.selectedRows.filter(row => !this.canDel(row));

                        for (const row of validRows) {
                            await axios.post('delete_file', {
                                filename: row.name
                            });
                        }

                        if (invalidRows.length > 0) {
                            const invalidFileNames = invalidRows.map(row => row.name).join(', ');
                            this.$message.warning(`批量删除成功!!! 以下文件因不满足删除条件未被删除: ${invalidFileNames}`);
                        } else {
                            this.$message.success('批量删除成功');
                        }

                        this.fetchFileList();

                    } catch (error) {
                        console.log('批量删除失败');
                        this.$message.error('批量删除失败: ' + error.message);
                    }
                },

                // 部署相关方法
                showDeploymentDialog(file) {
                    this.selectedFile = file;
                    this.deploymentDialogVisible = true;
                },

                handleServerSelection(servers) {
                    this.selectedServers = servers;
                },

                async startDeployment() {
                    if (!this.selectedServers.length) {
                        this.$message.warning('Please select at least one server');
                        return;
                    }

                    this.deploying = true;
                    try {
                        const response = await fetch('deploy_file', {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            body: JSON.stringify({
                                file: this.selectedFile.name,
                                servers: this.selectedServers
                            })
                        });

                        const result = await response.json();
                        this.handleDeploymentResult(result);
                    } catch (error) {
                        this.$message.error('Deployment failed: ' + error.message);
                    } finally {
                        this.deploying = false;
                        this.deploymentDialogVisible = false;
                    }
                },

                // 上传更新程序
                openUpgradeFileDialog() {
                    document.getElementById('upgrade-file-input').click();
                },
                // 处理文件选择事件
                async handleUpgradeFileSelect(event) {
                    const file = event.target.files[0];
                    if (file) {
                        try {
                            const formData = new FormData();
                            formData.append('file', file);
                            const response = await fetch('upgrade_program', {
                                method: 'POST',
                                body: formData
                            });

                            if (!response.ok) {
                                throw new Error(`HTTP ${response.status}`);
                            }

                            const data = await response.json();
                            if (data.code !== 0) {
                                throw new Error(data.msg);
                            }

                            this.$message.success('Upgrade program uploaded successfully');
                        } catch (error) {
                            this.$message.error('Failed to upload upgrade program: ' + error.message);
                        }
                    }
                },
                // 打开编辑配置对话框
                openEditConfigDialog() {
                    this.editConfigDialogVisible = true;
                    this.fetchConfig();
                },
                // 获取配置文件
                fetchConfig() {
                    axios.get('get_config_file')
                        .then(response => {
                            const data = response.data;
                            if (data.code === 0) {
                                try {
                                    const configData = JSON.parse(data.data);
                                    this.configText = JSON.stringify(configData, null, 4);
                                    // 提取 OA 配置信息
                                    this.oaConfig = configData.oaConfig || {};
                                } catch (error) {
                                    console.error('Failed to parse config data:', error);
                                    this.$message.error('Failed to parse config data');
                                }
                            } else {
                                console.error('Failed to fetch config file:', data.msg);
                                this.$message.error('Failed to load config file');
                            }
                        })
                        .catch(error => {
                            console.error('Failed to fetch config file:', error);
                            this.$message.error('Failed to load config file');
                        });
                },
                // 保存配置文件
                saveConfig() {
                    try {
                        const config = JSON.parse(this.configText);
                        axios.post('save_config_file', { config: config })
                            .then(response => {
                                const data = response.data;
                                if (data.code === 0) {
                                    this.$message.success('Config saved successfully');
                                    this.editConfigDialogVisible = false;
                                } else {
                                    console.error('Failed to save config file:', data.msg);
                                    this.$message.error('Failed to save config file');
                                }
                            })
                            .catch(error => {
                                console.error('Failed to save config file:', error);
                                this.$message.error('Failed to save config file');
                            });
                    } catch (error) {
                        console.error('Invalid JSON format:', error);
                        this.$message.error('Invalid JSON format');
                    }
                },
                // 根据文件类型获取图标类名
                getIconClass(fileType) {
                    return this.fileIconMap[fileType] || this.defaultIconClass;
                },
                openOApage() {
                    if (this.oaAccount) {
                        const { sendUrl } = this.oaConfig;
                        const formattedSendUrl = sendUrl.replace('{oaAccount}', this.oaAccount);
                        // 使用 axios 请求 gettoken 接口
                        axios.get(formattedSendUrl)
                            .then(response => {
                                const token = response.data; // 假设返回的数据中有 token 字段
                                const url = `${this.selectedOption}/?sso_sessionid=${token}`;
                                window.open(url, '_blank');
                            })
                            .catch(error => {
                                console.error('获取令牌失败:', error);
                                this.$message.error('获取令牌失败');
                            });
                    } else {
                        this.$message.warning('请输入OA账号');
                    }
                },
                selectPerson(row) {
                    this.oaAccount = row.iscAccount;
                },
                handleFolderClick(folder) {
                    this.uploadFolder = folder.path.replaceAll("uploads/", "");
                    console.log(`Clicked on folder: ${this.uploadFolder}`);

                    this.fetchFileList();
                },
                goToRoot() {
                    this.uploadFolder = '';
                    this.fetchFileList();
                },
                navigateToFolder(index) {
                    const parts = this.uploadFolder.split('/').filter(Boolean).slice(0, index + 1);
                    this.uploadFolder = parts.join('/');
                    this.fetchFileList();
                },
                generatePath(index) {
                    const parts = this.uploadFolder.split('/').slice(0, index + 1);
                    return parts.join('/');
                },
                goBackOneLevel() {
                    const lastIndex = this.uploadFolder.lastIndexOf('/');
                    if (lastIndex !== -1) {
                        this.uploadFolder = this.uploadFolder.slice(0, lastIndex);
                        this.fetchFileList();
                    } else if (this.uploadFolder !== "") {
                        this.uploadFolder = "";
                        this.fetchFileList();
                    } else {
                        this.$message.warning('已经是根目录');
                    }
                },

                handleDragOver(event) {
                    event.preventDefault();
                    event.stopPropagation();
                    // 添加样式提示用户可以放下文件
                    document.body.classList.add('dragover-indicator');
                    // 显示文字提示
                    document.getElementById('drag-drop-message').style.display = 'block';
                },
                handleDragLeave(event) {
                    event.preventDefault();
                    event.stopPropagation();
                    // 移除样式
                    document.body.classList.remove('dragover-indicator');
                    // 隐藏文字提示
                    document.getElementById('drag-drop-message').style.display = 'none';
                },

                // 页面拖入文件时的处理
                handleDrop(event) {
                    event.preventDefault();
                    event.stopPropagation();
                    // 移除样式
                    document.body.classList.remove('dragover-indicator');
                    // 隐藏文字提示
                    document.getElementById('drag-drop-message').style.display = 'none';
                    const files = event.dataTransfer.files;
                    if (files.length > 0) {
                        for (let i = 0; i < files.length; i++) {
                            const fileInfo = {
                                id: this.nextFileId++,
                                name: files[i].name,
                                rawFile: files[i],
                                size: files[i].size,
                                status: 'uploading',
                                progress: 0,
                                uploadedChunks: 0,
                                chunks: Math.ceil(files[i].size / CHUNK_SIZE), // 计算总块数
                                cancelToken: { canceled: false },
                                speed: null,
                                lastUpdate: Date.now()
                            };
                            this.uploadQueue.push(fileInfo);
                        }

                    }
                },

                // 新增：打开文件选择器
                openFileSelector() {
                    document.getElementById('fileInput').click();
                },
                // 新增：处理文件选择事件
                async handleFileInputChange(event) {
                    const files = event.target.files;
                    if (files.length > 0) {
                        for (let i = 0; i < files.length; i++) {
                            const fileInfo = {
                                id: this.nextFileId++,
                                name: files[i].name,
                                rawFile: files[i],
                                size: files[i].size,
                                status: 'uploading',
                                progress: 0,
                                uploadedChunks: 0,
                                chunks: Math.ceil(files[i].size / CHUNK_SIZE),
                                cancelToken: { canceled: false },
                                speed: null,
                                lastUpdate: Date.now()
                            };
                            this.uploadQueue.push(fileInfo);
                        }
                    }
                    // 清空文件输入框，以便下次选择相同文件时也能触发 change 事件
                    event.target.value = '';
                },
            }
        });
    </script>
</body>

</html>

<style>
    .upload-demo {
        margin-right: 10px;
    }

    .el-upload-dragger {
        width: 360px;
        height: 180px;
        padding: 20px;
    }

    .el-upload__text {
        margin-top: 10px;
    }


    /* 共享剪贴板卡片样式 */
    .clipboard-card:hover {
        transform: translateY(-2px);
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
    }

    .clipboard-card:active {
        transform: translateY(0);
    }

    /* 响应式布局 */
    @media (max-width: 768px) {
        .clipboard-card {
            grid-template-columns: 1fr !important;
        }
    }

    /* 动画效果 */
    .clipboard-card {
        animation: fadeInUp 0.5s ease;
    }

    @keyframes fadeInUp {
        from {
            opacity: 0;
            transform: translateY(20px);
        }

        to {
            opacity: 1;
            transform: translateY(0);
        }
    }
</style>